Skip to content

feat: Cookies#3406

Merged
RobinTail merged 45 commits into
masterfrom
feat-cookies
May 20, 2026
Merged

feat: Cookies#3406
RobinTail merged 45 commits into
masterfrom
feat-cookies

Conversation

@RobinTail
Copy link
Copy Markdown
Owner

@RobinTail RobinTail commented May 18, 2026

Related to discussion #710

Includes:

  • cookie parser on demand;
  • cookie operation middleware;
  • cookie as input source;
  • cookie as request parameter (Documentation)

Summary by CodeRabbit

  • New Features

    • Optional cookie parsing support with cookies and signed cookies usable as input sources
    • Middleware helper exposing set/clear cookie utilities (exported for use)
    • Cookie-backed security schemes reflected in generated API docs
    • Server will mount cookie parsing when cookie support is enabled
  • Documentation

    • Added guide, examples, and TOC entry for enabling/using cookies as input sources and documenting cookie security
  • Tests

    • New and updated tests covering cookie parsing, middleware helpers, and security-name extraction

Review Change Stack

@RobinTail RobinTail added documentation Improvements or additions to documentation enhancement New feature or request labels May 18, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds configurable cookie-parser integration, new cookies/signedCookies input sources, a public createCookieMiddleware() exposing setCookie/clearCookie, server wiring for conditional cookie parsing, security-name extraction, OpenAPI documentation updates, tests, and README/CHANGELOG entries for v28.1.0.

Changes

Cookie Support Feature

Layer / File(s) Summary
Configuration types and package dependencies
express-zod-api/src/config-type.ts, express-zod-api/package.json, express-zod-api/tests/config-type.spec.ts
InputSource type union expanded to include cookies and signedCookies; ServerConfig adds optional cookies property supporting boolean or CookieParserOptions; cookie-parser and @types/cookie-parser declared as optional peer and added to devDependencies.
Server-level cookie parser setup
express-zod-api/src/server-helpers.ts, express-zod-api/src/server.ts, express-zod-api/tests/express-mock.ts, express-zod-api/tests/server.spec.ts
createCookieParser factory loads cookie-parser peer at runtime and extracts secret/options from config; server conditionally registers middleware when config.cookies is truthy; tests and mocks assert registration and secret propagation.
Cookie context helpers
express-zod-api/src/cookie-middleware.ts, express-zod-api/src/index.ts, express-zod-api/tests/cookie-middleware.spec.ts
createCookieMiddleware factory exposes setCookie(name, value, overrides?) and clearCookie(name, overrides?) helpers to endpoint handlers, merging base and per-call options; middleware is re-exported from package entry and covered by behavioral tests.
Cookie input source integration
express-zod-api/tests/common-helpers.spec.ts
Tests verify that cookies and signedCookies from Express Request are merged into getInput() output when enabled via inputSources configuration.
Security name extraction utility
express-zod-api/src/security.ts, express-zod-api/tests/security.spec.ts
getSecurityNames helper recursively traverses LogicalContainer<Security> structures and extracts name values filtered by security type; handles nested and/or compositions and returns a deduplicated Set<string>.
OpenAPI documentation for cookies
express-zod-api/src/documentation-helpers.ts, express-zod-api/src/documentation.ts, express-zod-api/tests/documentation-helpers.spec.ts, express-zod-api/tests/documentation.spec.ts
depictRequestParams refactored to accept securityHeaders and securityCookies as Set<string> instead of security containers; defaultIsHeader updated to use Set membership; cookie parameters are emitted with in: "cookie" and documentation tests cover CookieSecurity representation.
User-facing documentation
CHANGELOG.md, README.md
CHANGELOG documents v28.1.0 cookie feature; README adds "Cookies as an input source" section with configuration guidance, createCookieMiddleware() usage example, and middleware security declaration examples.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • RobinTail/express-zod-api#3351: Both PRs modify express-zod-api/src/documentation-helpers.ts to use Set-based header-name lookup (defaultIsHeader switching to Set/.has() instead of array/.includes()).

Suggested labels

dependencies

Poem

🐰 I hopped into code with a jar of treats,
Cookies now travel through middleware streets,
setCookie and clearCookie, helpers at play,
Docs, tests, and changelog all lined up today,
🍪🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: Cookies' is vague and uses a generic term that doesn't convey meaningful information about the specific changes in the changeset. Consider a more descriptive title that specifies the primary change, such as 'feat: Add cookie parser support and cookie-based authentication' or 'feat: Implement cookies as input source with middleware support'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-cookies

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coveralls-official
Copy link
Copy Markdown

coveralls-official Bot commented May 18, 2026

Coverage Status

Coverage is 100.0%feat-cookies into master. No base build found for master.

Comment thread express-zod-api/src/config-type.ts
Comment thread express-zod-api/src/cookie-middleware.ts Outdated
Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues. Reviewed the following changes:

  • Widened setCookie value type from string to string | FlatObject and noted in JSDoc that Express JSON-serializes object values (matches Express's runtime 'j:' + JSON.stringify(value) path).
  • Retyped the destructured setCookie / clearCookie in the spec against the middleware's actual execute() return type instead of responseMock["cookie"], which guards the test from drifting if the helper signatures narrow further.
  • Added a regression case asserting the object-value path passes through to response.cookie.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues. Reviewed the following changes:

  • Replaced FlatObject with z.core.util.JSONType for setCookie's value type, broadening it from string/object to Zod's recursive JSON type (string, number, boolean, null, arrays, nested objects) — which matches Express's runtime branch (typeof val !== "string" ? 'j:' + JSON.stringify(val) : val) exactly.
  • Updated the JSDoc from "object values" to "non-string values" to reflect the wider type.
  • Dropped the now-unused FlatObject import.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the following changes:

  • Added a getCookie(name) helper to createCookieMiddleware's context that reads signedCookies[name] ?? cookies[name], presented as an alternative to declaring cookies in inputSources. ?? (rather than ||) correctly preserves false returned by cookie-parser when a signed cookie's signature fails, instead of silently falling through to the raw value.
  • Folded the previously separate CookieSecurity README example into the main cookies snippet so both the input-source path and the getCookie path are shown side-by-side, with the security: { type: "cookie", name: "session" } middleware declaration kept for OpenAPI depiction.
  • Updated the cookie-middleware test to seed requestProps.cookies / signedCookies and assert that getCookie returns the signed value first and undefined for missing names; the destructured output now uses the middleware's own Awaited<ReturnType<...["execute"]>> for type safety.
  • Renamed the README section anchor Cookies as an input sourceCookies and updated the TOC accordingly.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread CHANGELOG.md Outdated
Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues. Reviewed the following changes:

  • Wired the new cookie feature into the example/ workspace as a live demo: added loginEndpoint (setCookie via cookieAssistedFactory) and switched uploadAvatarEndpoint to cookieAuthenticatedFactory so session arrives as a cookie input.
  • Added sessionMiddleware and cookieAssistingMiddleware plus matching cookieAuthenticatedFactory / cookieAssistedFactory; enabled cookies: true in config.ts, added "cookies" to post input sources and the cookies tag.
  • Regenerated example.client.ts and example.documentation.yaml snapshots for the new endpoint and cookie-parameter, and added integration tests covering both the Set-Cookie response path (login) and cookie-as-input-source path (upload).
  • Added @desc JSDoc to the new and changed example endpoints.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues. Reviewed the following changes:

  • Simplified loginEndpoint: collapsed the credentials branch into assert(..., createHttpError(401, "Invalid credentials")), narrowed the output to { message }, and removed the success: boolean failure path. Regenerated client/yaml snapshots and updated the integration test to match. Now surfaces a real 401 instead of a 200 with success: false, consistent with how the rest of the example endpoints signal auth failures via http-errors.
  • Aligned uploadAvatarEndpoint's session check with the same pattern: assert(session.token, createHttpError(401, "Unauthorized")) replaces the bare throw new Error("Unauthorized"), which previously fell through to a 500 in the default result handler.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Owner Author

@RobinTail RobinTail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏁 ready

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues. Reviewed the following changes:

  • Tightened loginEndpoint's input schema from z.string().optional() to z.string().trim().nonempty() for both username and password. Regenerated example.client.ts (required string types) and example.documentation.yaml (minLength: 1, required: [username, password], requestBody.required: true). The existing integration test passes "admin"/"test" literals, so it remains green.

Pullfrog  | View workflow run | Using Claude Opus𝕏

@RobinTail
Copy link
Copy Markdown
Owner Author

@pullfrog please review the whole feature again though the lenses of security, performance and stability concerns

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues. Reviewed the following changes:

  • Narrowed clearCookie's per-call overrides from CookieOptions to Omit<CookieOptions, "expires" | "maxAge"> with an inline comment. Express 5's res.clearCookie force-sets expires = new Date(1) and delete opts.maxAge (see express/lib/response.js), so any caller-supplied values for those keys are silently discarded — the narrower type now surfaces that at compile time. Addresses the earlier note flagging this Express quirk. Factory-level baseOptions keep the full CookieOptions type, which is correct because those values are still meaningful for setCookie.
  • Tightened the corresponding test assertion: dropped the // unstable comment on expires: expect.any(Date) and replaced toBeLessThan(10) // usually 1 with exact toBe(1). Number(new Date(1)) is deterministic, so this is a strict improvement.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Re-review through the security / performance / stability lenses. Framework-side code is solid — the issues are concentrated in the example and the README, which is what users copy. Two things worth addressing before merge: missing CSRF guidance in the new Cookies section, and an undocumented footgun around mixing cookies and signedCookies in inputSources (later entries win, so ["signedCookies", "cookies"] lets an attacker override a signed value with a forged unsigned one). The login example also models a few patterns that should not be cargo-culted.

TL;DR — Adds optional cookie-parser integration: cookies / signedCookies as input sources, a createCookieMiddleware that exposes getCookie / setCookie / clearCookie into context, and OpenAPI rendering of CookieSecurity request parameters. The framework wiring follows the established optional-peer pattern; review focus is the user-facing surface (README, example).

Key changes

  • Optional cookie-parser integrationconfig.cookies (true or CookieParserOptions) gates a loadPeer<typeof cookieParser> call at startup; the parser is installed before beforeRouting.
  • Cookies as input sources"cookies" and "signedCookies" join InputSource, merged into getInput() like any other source.
  • createCookieMiddleware(baseOptions?) — returns a Middleware exposing getCookie (signed-first), setCookie, clearCookie with merged baseOptions.
  • CookieSecurity rendered as cookie paramsgetSecurityNames(containers, type) walks LogicalContainer<Security> without combinatorial expansion; depictRequestParams switched from a security alternatives list to pre-extracted securityHeaders / securityCookies sets.
  • Example refactor — new loginEndpoint and a cookieAuthenticatedFactory wired into the avatar upload to demonstrate cookie-driven auth.

Summary | 33 files | 42 commits | base: masterfeat-cookies


Security

The framework code itself is fine — getCookie correctly preferring signedCookies over cookies makes name collisions safe, and cookie-parser places tampered signed cookies under cookies (signature stripped), so the precedence rejects forgeries. The concerns are documentation and example shape:

  • CSRF: enabling cookie-backed auth introduces an attack surface that this framework didn't previously have. The new README section should mention sameSite defaults and CSRF tokens for state-changing endpoints.
  • Input-source ordering: getInput merges sources in array order via Object.assign. If a user writes inputSources: { get: ["signedCookies", "cookies"] }, an attacker can override a signed value by also sending an unsigned cookie of the same name. Worth a one-liner in the docs to put cookies before signedCookies (or omit cookies entirely when authentication relies on signing).

README.md · example/endpoints/login.ts · example/middlewares.ts


Performance

Before: depictRequestParams received the full Alternatives<Security> (combinatorial expansion via processContainers) just to extract header names.
After: getSecurityNames walks the raw LogicalContainer<Security> tree once per type, returning a Set; defaultIsHeader switched its familiar lookup from Array.includes to Set.has.

Net win on the documentation path — the combinatorial expansion is now only done once, for the actual depictSecurity call. No hot-path concerns: createCookieParser runs once at startup, and the per-request cost is whatever cookie-parser itself adds (standard Express middleware).

src/security.ts · src/documentation-helpers.ts


Stability

Edge cases I checked:

  • config.cookies: true (boolean) — destructuring through { ...(typeof config.cookies === "object" && config.cookies) } correctly collapses to {}, passing undefined to cookie-parser.
  • "signedCookies" listed in inputSources without a secret — request.signedCookies is undefined, Object.assign(target, undefined) no-ops. Test at common-helpers.spec.ts:226 covers this.
  • "cookies" listed in inputSources without config.cookies enabled — same graceful absence.
  • No public-API break that would require a migration rule: depictRequestParams and defaultIsHeader are internal (not re-exported from src/index.ts).

The implementation is solid. All findings below are about the user-facing example/docs.

src/server-helpers.ts · tests/common-helpers.spec.ts

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread README.md
Comment thread example/endpoints/login.ts Outdated
Comment thread example/endpoints/login.ts
Comment thread example/middlewares.ts Outdated
@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 20, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​cookie-parser@​1.4.101001008481100
Addedcookie-parser@​1.4.710010010083100

View full report

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues. Reviewed the following changes:

  • Added sameSite: "lax" to the createCookieMiddleware recommended base options in the README example, addressing the prior review's CSRF guidance gap. lax is the standard primary mitigation for cookie-backed auth and aligns with modern browser defaults.
  • Added a parenthetical (the order [matters](#customizing-input-sources)!) next to the inputSources mention in the new Cookies section, linking to the existing section that already documents the later-wins (Object.assign) precedence. This addresses the prior review's input-source-ordering footgun concern — and the example itself already models the safe ordering by putting signedCookies last, so signed values win on name collision.
  • TOC anchor link verified (#customizing-input-sources resolves to README L771).

Pullfrog  | View workflow run | Using Claude Opus𝕏

@RobinTail RobinTail merged commit 7b1919e into master May 20, 2026
14 checks passed
@RobinTail RobinTail deleted the feat-cookies branch May 20, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant